/*
 @author Duncan Grant aca08dg@shef.ac.uk
 Copyright 2011 Duncan Grant
*/
// (no jquery)
// 1. More coding of this to be done. Regex first!
// A test page: http://www.w3.org/2006/07/SWD/RDFa/impl/js/rdfa-calendar.html
(function () {
    
    var output;
    var button;
    var validAttributes = /rel|rev|content|href|src|about|property|resource|datatype|typeof/;
    var subjectAttributes = /about/;
    var predicateAttributes = /rel|rev|property/;
    var objectAttributes = /content|href|src|resource/;
    var pagesource = getPageSource();
    var currentTab = 'VIEW';
    var jsonOntologies = null;
    var rangeNum = 1;
    var tripleStore = [];
    var ontologiesUsed = [];
    // number of classes
    var numClasses = 0;
    var definingTriple = true;
    var finishedFile = '';
    var hoverBoxID = 0;
    
    // Client-side bookmarklet functions
    var Radify = function() {
        // instantiate HTML + CSS
        loadCSS("style.css");
        // return pagesource
    };
    
    // Specifically server communications
    var Server = function() {
        
    };
    
    Server.getOntologies = function() {
        var request = makeHttpObject();
        request.open("GET", "http://www.duncangrant.co.uk/radify/ontologyRequest.php", false);
        Server.displayLoading();
        request.send(null);
        if (request.status == 200) {
            // eval transforms the responseText from a string into an actual javascript object (JSON)
            // JSON.parse is safer than eval (compatible FF3.5+ not sure on other browsers)
            jsonOntologies = JSON.parse(request.responseText);
        } else {
            document.getElementById("radifyDisplay").innerHTML = "Error: could not speak to server.";
        }
    };
    
    Server.displayLoading = function() {
        var loadingOverlay = document.createElement("div");
        loadingOverlay.setAttribute("id", "radifyLoadingOverlay");
        if (document.getElementById("radifyLoadingOverlay") === null) {
            document.body.appendChild(loadingOverlay);
            console.log(document.documentElement.clientHeight);
            // make loading overlay height perfect
            var loadingHeight = ((document.documentElement.clientHeight) - 60) + 'px';
            document.getElementById("radifyLoadingOverlay").style.height = loadingHeight;
            document.getElementById("radifyLoadingOverlay").innerHTML = '<div id="radifyLoading"><img src="http://www.duncangrant.co.uk/radify/loading.gif" /></div>';
        }
    };
    
    Server.removeLoadingOverlay = function() {
        if (document.getElementById("radifyLoadingOverlay") !== null) {
            Radify.removeAllChildren(document.getElementById("radifyLoadingOverlay"));
            document.getElementById("radifyLoadingOverlay").parentNode.removeChild(document.getElementById("radifyLoadingOverlay"));
        }
    };
    
    Server.displayOntologies = function(jsonResponse) {
        
    };
    
    // cross-browser events
    Radify.event = function(elem, action, handler, bubble) {
        if((typeof addEventListener) === 'function') {
            elem.addEventListener(action, handler, bubble);
        } else {
            // this will most likely be IE
            try {
                elem.attachEvent(('on' + action), handler);
            } catch (error) {
                // put error message on output div
                // for now just using alert
                alert(error);
            }
        }
    };
    
    Radify.loadView = function() {
        currentTab = 'VIEW';
        var html = '<div id="radifyHeader"><div id="radifyTitle"><h1>RADiFy</h1></div>\
            <div class="buttons" id="radifyMinmax"><img src="http://www.duncangrant.co.uk/radify/min.png" alt="Minimize" /></div> \
            <div class="buttons" id="radifyClose"><img src="http://www.duncangrant.co.uk/radify/close.png" alt="Close" /></div> \
            </div><div id="radifyContent"><div id="radifyTabs"><ul><li><a href="#" id="radifyViewTab">View</a></li> \
            <li><a href="#" id="radifyEditTab">Edit</a></li></ul></div> \
            <div id="radifyDisplay"><label for="radifyOntologies">Ontologies</label> \
            <div id="radifyOntologies" class="info"></div><label for="radifyTriples">Triples</label> \
            <div id="radifyTriples" class="info"></div><label for="radifyServices">Web Services</label> \
            <div id="radifyServices" class="info"></div><div id="radifySpacer"></div></div></div>';
        // checks if container is there
        // if it is then delete children and add new html
        if(document.getElementById("radifyContainer") != null) {
            // load view html
            Radify.removeAllChildren(document.getElementById("radifyContainer"));
            document.getElementById("radifyContainer").innerHTML = html;
        } else {
            var container = document.createElement("div");
            container.setAttribute("id", "radifyContainer");
            document.body.appendChild(container);
            document.getElementById("radifyContainer").innerHTML = html;
        }
        Radify.setListeners();
        Radify.displayTriples();
        Radify.displayNamespaces();
    };
    
    var subjectHTML = '<p><b>Defining a triple</b></p><label for="radifyChooseSubject">Subject \
        <span id="radifySubjectHelp" class="radifyHelp">[help]</span></label> <input type="text" value="" \
        name="subject" id="radifyChooseSubject" /><label \
        for="radifyPredicateTag">Predicate Tag</label> \
        <select id="radifyPredicateTag" name="predicateTag"> \
        <option></option> </select>';
    
    var classHTML = '<p><b>Defining a class</b></p><label for="radifyClasses">Select a Class \
        <span id="radifyClassHelp" class="radifyHelp">[help]</span></label><select id="radifyClasses" name="ontologyClasses"><option></option></select>';
    
    Radify.loadEdit = function() {
        currentTab = 'EDIT';
        var html = '<div id="radifyHeader"><div id="radifyTitle"><h1>RADiFy</h1></div><div class="buttons" id="radifyMinmax"> \
        <img src="http://www.duncangrant.co.uk/radify/min.png" alt="Minimize" /></div><div class="buttons" id="radifyClose"> \
        <img src="http://www.duncangrant.co.uk/radify/close.png" alt="Close" /></div></div><div id="radifyContent"> \
        <div id="radifyTabs"><ul><li><a href="#" id="radifyViewTab">View</a></li><li> \
        <a href="#" id="radifyEditTab">Edit</a></li></ul></div><div id="radifyDisplay"> \
        <label for="radifyOntologySelect">Ontology [load] <span id="radifyOntologyHelp" class="radifyHelp">[help]</span></label><select \
        id="radifyOntologySelect" name="ontology"><option></option></select> \
        <div class="radifySpacerClass">Define a <span id="radifyDefineTriple">TRIPLE</span> \
        or a <span id="radifyDefineClass">CLASS</span></div> \
        <div id="radifySubjectClass"></div> \
        <p class="radifyCentre"><button type="button" id="radifyHighlight">Highlight Text</button></p> \
        <label for="radifyObject">Object <span id="radifyObjectHelp" class="radifyHelp">[help]</span></label> <input type="text" \
        name="object" id="radifyObject" readonly="readonly" /> \
        <label for="radifyStatus">Status</label> <div id="radifyStatus" \
        class="info"></div> \
        <p class="radifyCentre"><button name="learn" id="radifyLearnButton" type="button">Learn</button> <button name="save" \
        id="radifySaveTriple" type="button">Save</button> <button \
        name="finish" id="radifyFinishEdit">Finished!</button></span> </p> \
        <div id="radifySpacer"></div></div></div>';
        if(document.getElementById("radifyContainer") != null) {
            Radify.removeAllChildren(document.getElementById("radifyContainer"));
            document.getElementById("radifyContainer").innerHTML = html;
        } else {
            var container = document.createElement("div");
            container.setAttribute("id", "radifyContainer");
            document.body.appendChild(container);
            document.getElementById("radifyContainer").innerHTML = html;
        }
        // contact server to get ontology information (quite a lot of info to retrieve)
        if (jsonOntologies === null) {
            Server.getOntologies();
        }
        // get dynamic data from server and load into Radify
        var ontologyName = '';
        // Default: no class (as it is not necessary)
        for (var i = 0; i < jsonOntologies.length; i++) {
            ontologyName += '<option value="' + jsonOntologies[i].name + '">' + jsonOntologies[i].name + '</option>';
            var status = '<p><b>Loaded ontology: ' + jsonOntologies[i].name + '</b></p><p>' + jsonOntologies[i].description + '</p>';
        }
        var status = '<p><b>Loaded ontology: ' + jsonOntologies[0].name + '</b></p><p>' + jsonOntologies[0].description + '</p>';
        document.getElementById("radifyOntologySelect").innerHTML = ontologyName;
        document.getElementById("radifyStatus").innerHTML = status;
        // editing triples by default
        Radify.loadSubject();
        Radify.setHelpers();
        Server.removeLoadingOverlay();
    };
    
    Radify.loadClass = function() {
        definingTriple = false;
        var div = document.getElementById("radifySubjectClass");
        div.innerHTML = classHTML;
        var opt = document.getElementById("radifyOntologySelect").selectedIndex;
        var classes = '';
        // load classes
        numClasses = 0;
        for (var i = 0; i < jsonOntologies[opt].properties.length; i++) {
            // only select elements which are classes
            if (jsonOntologies[opt].properties[i].isClass) {
                classes += '<option value="' + jsonOntologies[opt].properties[i].CURIE + '">' + jsonOntologies[opt].properties[i].CURIE + '</option>';
                numClasses++;
            }
        }
        // if there are no classes for this ontology to display
        if (numClasses == 0) {
            Radify.setStatus("No classes for this ontology. Can only make triples.", 0);
            Radify.loadSubject();
        }
        // else display the classes
        else {
            document.getElementById("radifyClasses").innerHTML = classes;
        }
        Radify.setListeners();
    };
    
    Radify.loadSubject = function() {
        definingTriple = true;
        var div = document.getElementById("radifySubjectClass");
        div.innerHTML = subjectHTML;
        var opt = document.getElementById("radifyOntologySelect").selectedIndex;
        var labels = '';
        console.log(jsonOntologies[opt].properties.length);
        console.log(opt);
        for (var i = 0; i < jsonOntologies[opt].properties.length; i++) {
            // label can be blank sometimes, perhaps use URI minus the namespace?
            // make sure not class
            if (!jsonOntologies[opt].properties[i].isClass) {
                labels += '<option value="' + jsonOntologies[opt].properties[i].CURIE + '">' + jsonOntologies[opt].properties[i].CURIE + '</option>';
            }
        }
        document.getElementById("radifyPredicateTag").innerHTML = labels;
        Radify.setListeners();
    };
    
    Radify.removeAllChildren = function(parent) {
        while(parent.hasChildNodes()) {
            parent.removeChild(parent.firstChild);
        }
    };
    
    Radify.setHelpers = function() {
        var ontologyHelp = document.getElementById("radifyOntologyHelp");
        Radify.event(ontologyHelp, 'mouseover', Radify.hoverFunc, false);
        Radify.event(ontologyHelp, 'mouseout', Radify.hoverOut, false);
        if (document.getElementById("radifySubjectHelp") != undefined) {
            var subjectHelp = document.getElementById("radifySubjectHelp");
            Radify.event(subjectHelp, 'mouseover', Radify.hoverFunc, false);
            Radify.event(subjectHelp, 'mouseout', Radify.hoverOut, false);
        }
        if (document.getElementById("radifyClassHelp") != undefined) {
            var classHelp = document.getElementById("radifyClassHelp");
            Radify.event(classHelp, 'mouseover', Radify.hoverFunc, false);
            Radify.event(classHelp, 'mouseout', Radify.hoverOut, false);
        }
        if (document.getElementById("radifyObjectHelp") != undefined) {
            var objectHelp = document.getElementById("radifyObjectHelp");
            Radify.event(objectHelp, 'mouseover', Radify.hoverFunc, false);
            Radify.event(objectHelp, 'mouseout', Radify.hoverOut, false);
        }
    };
    
    Radify.hoverFunc = function(elem) {
        var div = document.createElement("div");
        div.setAttribute("class", "radifyHoverBox");
        var topOffset = elem.target.offsetTop;
        console.log(topOffset);
        var pos = "top:" + topOffset + "px";
        div.setAttribute("style", pos);
        div.setAttribute("id", elem.target.innerHTML);
        hoverBoxID++;
        document.body.appendChild(div);
        var msg = '';
        // get id and then make msg
        if (elem.target.id == "radifyOntologyHelp") {
            msg = 'An ontology specifies a library of terms which can be used to describe data.';
        }
        else if (elem.target.id == "radifySubjectHelp") {
            msg = 'A subject is a URI. If you want to create a new subject relative to the current page \
                  then start the subject with a "#", for example <i>#my_subject_URI</i>. \
                  It is good practice to re-use URIs for resources if they have already been defined elsewhere \
                  in the World Wide Web, so try to do this if possible.';
        }
        else if (elem.target.id == "radifyClassHelp") {
            msg = 'A class defines a data item of a certain type. For example, highlight all of the text which refers to \
                  a single person and then define the text using the FOAF class "Person". Make sure to highlight the maximum \
                  amount of text which is in reference to exactly one class.';
        }
        else if (elem.target.id == "radifyObjectHelp") {
            msg = 'When defining a triple, an object is the end-point of a triple relation. When using RADiFy you will create objects by highlighting \
                  text on the web page and then clicking the "Highlight Text" button to make the selected text the object for your \
                  triple.<br /> However, if you are defining a Class then the object is the entire body of text which can be applied to that class.';
        }
        else {
            // else: unknown object
            msg = 'A help tip has not been created for this element: ' + elem.target.id;
        }
        div.innerHTML = msg;
        console.log(elem.target.id);
        console.log(parent);
        console.log("hi");
    };
        
    Radify.hoverOut = function(elem) {
        console.log(elem.target.innerHTML);
        var item = document.getElementById(elem.target.innerHTML);
        item.parentNode.removeChild(item);
    };
    
    Radify.getSelection = function() {
        try {
            var range = document.createRange();
            var sel = window.getSelection();
            range = sel.getRangeAt(0);
            if(!range.collapsed) {
                var new_elem = document.createElement('span');
                var id = "radifyObject" + rangeNum;
                new_elem.setAttribute('id', id);
                rangeNum++;
                
                txt = range.cloneContents();
                new_elem.appendChild(txt);
                
                range.deleteContents(); /* delete the current selection */
                range.insertNode(new_elem); /* add the newly created element */
                
                console.log(new_elem.tagName);
                console.log(new_elem.innerHTML);
                
                document.getElementById("radifyObject").value = new_elem.innerHTML;
                
                sel.removeAllRanges();
                /* debug */
                parseDOM.tree = [];
                parseDOM.traverse(document);
                console.log(parseDOM.tree);
            } else {
                alert("No selection was made.");
            }
        } catch (e) {
            alert("Error highlighting text: " + e);
        }
    };
    
    Radify.setListeners = function() {
        // close bookmarklet
        var close = document.getElementById("radifyClose");
        Radify.event(close, 'click', closeRadify, false);
        // minimize and maximize button
        var minmax = document.getElementById("radifyMinmax");
        Radify.event(minmax, 'click', Radify.minmax, false);
        if (currentTab == 'VIEW') {
            // make edit tab link clickable
            var editTab = document.getElementById("radifyEditTab");
            Radify.event(editTab, 'click', Radify.loadTab, false);
        }
        else if (currentTab == 'EDIT') {
            // make view tab clickable
            var viewTab = document.getElementById("radifyViewTab");
            Radify.event(viewTab, 'click', Radify.loadTab, false);
            // make selecting ontologies load different labels
            var selectedOntology = document.getElementById("radifyOntologySelect");
            Radify.event(selectedOntology, 'change', function() {
                var opt = selectedOntology.selectedIndex;
                if (definingTriple) {
                    Radify.loadSubject();
                }
                else {
                    Radify.loadClass();
                }
                Radify.setStatus("ontology", opt);
            }, false);
            var selectedCURIE = document.getElementById("radifyPredicateTag");
            numClasses = 0;
            var opt = selectedOntology.selectedIndex;
            for (var i = 0; i < jsonOntologies[opt].properties.length; i++) {
                // only select elements which are classes
                if (jsonOntologies[opt].properties[i].isClass) {
                    numClasses++;
                }
            }
            if (selectedCURIE != undefined) {
                Radify.event(selectedCURIE, 'change', function() {
                    // number of classes + predicate index number
                    var predicateID = numClasses + selectedCURIE.selectedIndex;
                    Radify.setStatus("curie", predicateID);
                }, false);
            }
            var defineClass = document.getElementById("radifyDefineClass");
            Radify.event(defineClass, 'click', Radify.loadClass, false);
            var defineTriple = document.getElementById("radifyDefineTriple");
            Radify.event(defineTriple, 'click', Radify.loadSubject, false);
            var highlightButton = document.getElementById("radifyHighlight");
            Radify.event(highlightButton, 'click', Radify.getSelection, false);
            var saveTripleButton = document.getElementById("radifySaveTriple");
            Radify.event(saveTripleButton, 'click', Radify.saveTriple, false);
            var finishButton = document.getElementById("radifyFinishEdit");
            Radify.event(finishButton, 'click', Radify.finish, false);
            // finally set the hover help boxes
            Radify.setHelpers();
        }
    };
    
    Radify.saveTriple = function() {
        var index = document.getElementById("radifyOntologySelect").selectedIndex;
        var id = rangeNum-1;
        var id = "radifyObject" + id;
        var span = document.getElementById(id);
        var msg = '';
        // defining Class
        if (!definingTriple) {
            var selectedClass = document.getElementById("radifyClasses").value;
            // if is defining a Class type and not a triple
            var type = jsonOntologies[index].nsName + ":" + selectedClass;
            span.setAttribute('typeof', type);
            msg = 'Defined a class of type: ';
            msg += selectedClass;
        }
        else {
            // defining Triple
            var subject = document.getElementById("radifyChooseSubject").value;
            console.log(jsonOntologies[index].namespace);
            var predicate = jsonOntologies[index].nsName + ":";
            predicate += document.getElementById("radifyPredicateTag").value;
            var object = document.getElementById("radifyObject").value;
            if (predicate == "" || object == "") {
                msg = 'Could not save triple (a value was not defined):<br />';
            }
            else {
                // save triple
                tripleStore.push({
                    'subject': subject,
                    'predicate': predicate,
                    'object': object
                });
                span.setAttribute('property', predicate);
                // if subject is specified then add "about" attribute
                if (subject.length > 0) {
                    span.setAttribute('about', subject);
                }
                // set msg
                msg = 'Saved triple. <br />';
                msg += 'subject: ' + subject + '<br />';
                msg += 'predicate: ' + predicate + '<br />';
                msg += 'object: ' + object + '<br />';
            }
        }
        // TODO: add namespace if it is not used elsewhere
        var namespaceDeclared = false;
        for (var i = 0; i < ontologiesUsed.length; i++) {
            // if ontology just added == an ontology already declared
            if (jsonOntologies[index].namespace == ontologiesUsed[i].uri) {
                namespaceDeclared = true;
                break;
            }
        }
        // if namespace is not already declared then add it to our list.
        if (!namespaceDeclared) {
            ontologiesUsed.push({
                "curie": jsonOntologies[index].nsName,
                "uri": jsonOntologies[index].namespace
            });
        }
        // display output
        Radify.setStatus(msg, 0);
        // debug
        // console.log(Radify.createNewHTML());
    };
    
    Server.fileFunctions = function(file) {
        var absoluteFile = "http://www.duncangrant.co.uk/radify/" + file;
        var html = '';
        html += '<p><a href="' + absoluteFile + '">Download HTML source code</a></p>';
        html += '<p id="radifyDeleteFile"><button type="button" id="radifyDeleteButton">Delete</button></p>';
        return html;
    };
    
    Server.deleteFile = function() {
        if (finishedFile != '') {
            var request = makeHttpObject();
            var f = encodeURIComponent(finishedFile);
            var html = '';
            request.open("GET", "http://www.duncangrant.co.uk/radify/deleteFile.php?file=" + f, true);
            request.onreadystatechange = function() {
                if (request.readyState == 4) {
                    if (request.status == 200) {
                        html += request.responseText;
                        console.log(html);
                        // check element exists!
                        if (document.getElementById("radifyDeleteFile")) {
                            // set output
                            document.getElementById("radifyDeleteFile").innerHTML = html;
                        }
                    }
                    else {
                        console.log("Error communicating with server. Status: " + request.statusText);
                    }
                }
            }
        }
        request.send(null);
    };
    
    Radify.finish = function() {
        if (tripleStore !== null) {
            var request = makeHttpObject();
            request.open('POST', 'http://www.duncangrant.co.uk/radify/saveRDFa.php', true);
            var triples = JSON.stringify(tripleStore);
            var origin = encodeURIComponent(window.location);
            var source = encodeURIComponent(Radify.createNewHTML());
            var params = 'source='+source+'&triples='+triples+'&origin='+origin;
            console.log(params);
            request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            request.setRequestHeader("Content-Length", params.length);
            request.onreadystatechange = function() {
                if (request.readyState == 4) {
                    if (request.status == 200) {
                        var display = document.getElementById("radifyDisplay");
                        Radify.removeAllChildren(display);
                        finishedFile = request.responseText;
                        display.innerHTML = Server.fileFunctions(finishedFile);
                        var deleteButton = document.getElementById("radifyDeleteButton");
                        Radify.event(deleteButton, 'click', Server.deleteFile, false);
                        // display.innerHTML = request.responseText;
                        // enable edit tab button (now both edit and view buttons can be clicked)
                        var editTab = document.getElementById("radifyEditTab");
                        Radify.event(editTab, 'click', Radify.loadEdit, false);
                    }
                    else {
                        console.log("Error communicating with server. Status: " + request.statusText);
                    }
                }
            };
            request.send(params);
        }
        else {
            Radify.setStatus("You must first save some triples.", 0);
        }
    };
    
    // creates new HTML source code for page
    Radify.createNewHTML = function() {
        // document.childNodes[1].innerHTML (works but picks up loads of junk from extensions [nope! it doesn't upon further testing])
        // HTML doc will be xhtml + rdfa
        var html = '<?xml version="1.0" encoding="UTF-8"?>\
                    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"\
                        "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">\n\
                    <html xmlns="http://www.w3.org/1999/xhtml" ';
        // populate ontology namespaces that were used.
        if (ontologiesUsed != null) {
            for (var i = 0; i < ontologiesUsed.length; i++) {
                html += 'xmlns:' + ontologiesUsed[i].curie + '="' + ontologiesUsed[i].uri + '" ';
                console.log('xmlns: ' + ontologiesUsed[i].curie + '="' + ontologiesUsed[i].uri + '" ');
            }
        }
        html += '>\n';
        // delete pesky "radifyObject" id's left in the generated span tags
        for (var i = 1; i < rangeNum; i++) {
            var elemID = "radifyObject" + i;
            var elem = document.getElementById(elemID);
            console.log(elemID);
            console.log(elem != undefined);
            console.log(rangeNum-1);
            if (elem != undefined) {
                elem.removeAttribute('id');
            }
        }
        // TODO: remove radify from source code!
        for (var i = 0; i < document.childNodes.length; i++) {
            if (document.childNodes[i].tagName == 'HTML') {
                // remove radify from page so it won't be in source code.
                Radify.removeAllChildren(document.getElementById("radifyContainer"));
                document.getElementById("radifyContainer").parentNode.removeChild(document.getElementById("radifyContainer"));
                html += document.childNodes[i].innerHTML;
                html += '</html>';
                // reloads radify (but is fast because doesn't need to contact server this time)
                Radify.loadEdit();
                return html;
            }
        }
        // if it gets here without returning then probably hasn't found HTML tag.
        return "Error generating HTML: Could not find HTML tag in document.";
    };
    
    Radify.setStatus = function(param, id) {
        if (param === "ontology") {
            var status = '<p><b>Loaded ontology: ' + jsonOntologies[id].name + '</b></p><p>' + jsonOntologies[id].description + '</p>';
            document.getElementById("radifyStatus").innerHTML = status;
        }
        else if (param === "curie") {
            var ontologyID = document.getElementById("radifyOntologySelect").selectedIndex;
            var comment = jsonOntologies[ontologyID].properties[id].comment;
            /*
            if (comment === null) {
                comment = "No comment has been made by the ontology.";
            }*/
            var status = '<p><b>' + jsonOntologies[ontologyID].properties[id].CURIE + '</b></p><p>' + comment + '</p>';
            document.getElementById("radifyStatus").innerHTML = status;
        }
        else {
            document.getElementById("radifyStatus").innerHTML = param;
        }
    };
    
    Radify.loadTab = function() {
        if (currentTab == 'EDIT') {
            Radify.loadView();
            currentTab = 'VIEW';
        } else {
            Radify.loadEdit();
            currentTab = 'EDIT';
        }
    };
    
    function closeRadify() {
        var container = document.getElementById("radifyContainer");
        container.parentNode.removeChild(container);
    };
    
    Radify.minmax = function() {
        // check if bookmarklet is loaded in DOM
        var content = document.getElementById("radifyContent");
        var container = document.getElementById("radifyContainer");
        var minmaxButton = document.getElementById("radifyMinmax");
        // has to exist!
        if(content != null) {
            // if is minimized
            if(content.style.display == "none") {
                content.style.display = "block";
                // change img
                minmaxButton.innerHTML = '<img src="http://www.duncangrant.co.uk/radify/min.png" alt="Minimize" />';
                // resize container
                container.style.height = '100%';
            } else {
                content.style.display = "none";
                // change img
                minmaxButton.innerHTML = '<img src="http://www.duncangrant.co.uk/radify/max.png" alt="Maximize" />';
                // change size of container (otherwise cannot select elements below invisible container)
                container.style.height = '30px';
            }
        }
    };
    
    function getPageSource() {
        var httpObject = makeHttpObject();
        httpObject.open("GET", location.pathname, false);
        httpObject.send(null);
        if (httpObject.status == 200) {
            return httpObject.responseText;
        } else {
            return null;
        }
    }
    
    function loadCSS(url) {
        var cssFile = document.createElement("link");
        cssFile.setAttribute("rel", "stylesheet");
        cssFile.setAttribute("type", "text/css");
        cssFile.setAttribute("id", "radifyCSS");
        cssFile.setAttribute("href", url);
        if(document.getElementById("radifyCSS") == null) {
            document.getElementsByTagName("head")[0].appendChild(cssFile);
        }
    }
    
    // send data. Split either POST or GET
    Radify.ajax = function(url, data, method) {
        
    };
    
    /*
     1. Go through DOM looking for triples
    */
    // ALMOST WORKS! I think...
    // Need to fix issue about referring to incorrect subject. Actually might not be an issue
    // due to tree structure of parsing.
    // Always use this method before get ontologies!
    Radify.getTriples = {
        docResource: '',
        triples: [],
        // make elem=document and currentSubject="<>" to correctly parse whole DOM
        parseDOM: function (elem, currentSubject) {
            if (!elem || elem === null) {
                throw new Error("Specified element: " + elem + " does not exist.");
            }
            var triple = {
                'subject': currentSubject,
                'predicate': '',
                'object': ''
            };
            // need to reset currentSubject at some point to default!
            if (elem.attributes !== null) {
                for (var i = 0; i < elem.attributes.length; i++) {
                    // if the attribute of an element is one used by RDFa then get stuff
                    if (validAttributes.test(elem.attributes[i].name)) {
                        // add attribute name and element to an array? Or some other structure.
                        // if attribute is changing subject, set new subject
                        if (subjectAttributes.test(elem.attributes[i].name)) {
                            triple.subject = elem.attributes[i].value;
                            // check if subject is relative
                            if (triple.subject.charAt(0) === "#") {
                                // make subject absolute URI
                                console.log(getDocumentURI());
                                triple.subject = getDocumentURI() + triple.subject;
                            }
                        }
                        // else if is predicate (object is innerHTML)
                        else if (predicateAttributes.test(elem.attributes[i].name) && /\w+:\w+/.test(elem.attributes[i].value)) {
                            triple.predicate = elem.attributes[i].value;
                            // specify ontology that is in use (specific to RDFa this way)
                            // check if used first
                            var unique = true;
                            var curie = elem.attributes[i].value.match(/(\w+):/)[1];
                            for (var j = 0; j < ontologiesUsed.length; j++) {
                                if (curie == ontologiesUsed[j].curie) {
                                    // curie is already stated
                                    unique = false;
                                }
                            }
                            // curie is not defined yet.
                            if (unique) {
                                ontologiesUsed.push({
                                    "curie": curie,
                                    "uri": ""
                                });
                            }
                            // get object from innerHTML of element (if it's there)
                            if ((triple.object === '') && ((typeof elem.innerHTML) !== 'undefined')) {
                                triple.object = elem.innerHTML;
                            }
                        }
                        // else if object
                        else if (objectAttributes.test(elem.attributes[i].name)) {
                            triple.object = elem.attributes[i].value;
                        }
                    }
                }
            }
            // if we have predicate but not object yet.
            if (triple.predicate !== '' && triple.object === '') {
                if((typeof elem.innerHTML) !== 'undefined') {
                    triple.object = elem.innerHTML;
                }
            }
            // store triple (should now be fully formed) if not fully formed then don't add! (not triple)
            if (triple.predicate !== '' && triple.object !== '') {
                Radify.getTriples.triples.push(triple);
            }
            // go through child elements
            for (var i = 0; i < elem.childNodes.length; i++) {
                Radify.getTriples.parseDOM(elem.childNodes[i], triple.subject);
            }
        }
    };
    
    /*
     Display the found triples on the actual bookmarklet.
    */
    Radify.displayTriples = function() {
        var show = document.getElementById("radifyTriples");
        // reset triples array on the rare chance that it has already been populated
        Radify.getTriples.triples = [];
        Radify.getTriples.parseDOM(document, Radify.getTriples.docResource);
        var triples = Radify.getTriples.triples;
        var html = '<ul>';
        for (var i = 0; i < triples.length; i++) {
            // for display purposes only, show relative URIs rather than absolute (where applicable)
            if (triples[i].subject.indexOf(getDocumentURI()) == 0) {
                triples[i].subject = triples[i].subject.slice(getDocumentURI().length);
            }
            html += '<li>' + '&lt;<span>' + triples[i].subject + '</span>&gt; ' + triples[i].predicate + ' "' + triples[i].object + '"</li>';
        }
        html += '</ul>';
        show.innerHTML = html;
        var clickableSubject = document.getElementById("radifyTriples").firstChild.childNodes;
        for (var i = 0; i < clickableSubject.length; i++) {
            var span = clickableSubject[i].firstChild;
            // make clickable.
        }
    };
    
    // gets whole DOM tree structure. Mainly for debugging.
    var parseDOM = {
        tree: [],
        traverse: function(elem) {
            if (!elem || elem === null) {
                throw new Error("Could not start traversing DOM from specified element: " + elem);
            }
            if ((typeof elem.tagName) !== 'undefined') {
                parseDOM.tree.push(elem);
            }
            var i = 0;
            var kid = elem.childNodes[i];
            while (kid) {
                parseDOM.traverse(kid);
                i++
                kid = elem.childNodes[i];
            }
        }
    };
    
    Radify.parse = function() {
        // get current base (the default namespace for CURIEs)
        // remove hash if there is one
        var currentBase = getDocumentURI().replace(/\#.*$/,'');
    };
    
    /*
     Parse current document:
     1. Get Namespaces:
        a. scan for string "xmlns:" within tags
        b. get namespace immediately after
        c. then get URI after "="
        d. some namespaces may be scope limited
        but most are contained within html tag
     
    */
    /* OLD VERSION: USES PAGE SOURCE AND NOT DOM!
    Radify.getNamespaces = function() {
        // NOT FINISHED.
        // rewrite to use DOM rather than source code?
        try {
            var nsList = {
                main: null,
                curies: []
            };
            var defaultns = /<.*xmlns=["']([^"']*)['"].*>/;
            // not checking within tags (wasn't working for multiple items) but should be ok in the context used.
            // new idea
            var curieRegex = /<[.*\s*]*xmlns:(\w*)=["']([^"']*)['"][.*\s*]*>/g;
            var getDefaultNS = pagesource.match(defaultns);
            // store CURIEs in nsList
            var getCURIE = pagesource.replace(curieRegex, function(match, name, URI) {
                nsList.curies.push({
                    'name': name,
                    'URI': URI
                });
                return (name + URI);
            });
            // add default ns (possibly not needed though)
            nsList.main = getDefaultNS[1];
            return nsList;
        } catch (e) {
            console.log(e);
            return e;
        }
    };
    */
    
    // DOM version (not source code) good!
    Radify.getNamespaces = function(elem) {
        if (elem && elem !== null && elem.attributes !== null) {
            var nsRegex = /xmlns:(\w+)/;
            for (var i = 0; i < elem.attributes.length; i++) {
                // if is namespace store as CURIE
                if (nsRegex.test(elem.attributes[i].name)) {
                    var ns = elem.attributes[i].name.match(nsRegex);
                    for (var j = 0; j < ontologiesUsed.length; j++) {
                        // if CURIE is RDFa (and in use on the page)
                        if (ns[1] == ontologiesUsed[j].curie) {
                            ontologiesUsed[j].uri = elem.attributes[i].value;
                            console.log("URI: " + elem.attributes[i].value);
                        }
                    }                
                }
            }
        }
        for (var i = 0; i < elem.childNodes.length; i++) {
            Radify.getNamespaces(elem.childNodes[i]);
        }
    };
    
    /*
     Display the found namespaces in the ontology box
     NEED TO MAKE SURE THIS IS DEFINITELY IN VALID N3!!!!
    */
    // remember: scan triples first!
    Radify.displayNamespaces = function() {
        var show = document.getElementById("radifyOntologies");
        // if RDFa is used on page
        if (ontologiesUsed != null) {
            Radify.getNamespaces(document);
            var html = '<ul><li style="text-align:center;background-color:#ff8080">All</li>';
            for (var i = 0; i < ontologiesUsed.length; i++) {
                html += ('<li>' + ontologiesUsed[i].curie + ': &lt;' + ontologiesUsed[i].uri + '&gt;</li>');
            }
            html += '</ul>'
            show.innerHTML = html;
        }
        // event listener for clicking on ontology (ontology filtering)
        var listItems = show.firstChild.childNodes;
        for (var i = 0; i < listItems.length; i++) {
            if (listItems[i].tagName == "LI") {
                Radify.event(listItems[i], 'click', Radify.filterOntologies, false);
            }
        }
    };
    
    Radify.filterOntologies = function(elem) {
        // display all
        console.log(elem.target.innerHTML);
        // change colors
        elem.target.style.backgroundColor = "#ff8080";
        var ontologyList =  document.getElementById("radifyOntologies").firstChild.childNodes;
        for (var i = 0; i < ontologyList.length; i++) {
            if (ontologyList[i] != elem.target) {
                // reset background colors for ontologies not selected
                ontologyList[i].style.backgroundColor = "#ffbbbd";
            }
        }
        if (elem.target.innerHTML == "All") {
            var listItems = document.getElementById("radifyTriples").firstChild.childNodes;
            for (var i = 0; i < listItems.length; i++) {
                listItems[i].style.display = "block";
            }
        }
        else {
            var ont = (elem.target.innerHTML).match(/(\w+)/);
            // console.log(ont[1]);
            var listItems = document.getElementById("radifyTriples").firstChild.childNodes;
            for (var i = 0; i < listItems.length; i++) {
                if (listItems[i].innerHTML.match(/(\w+):/)[1] == ont[1]) {
                    listItems[i].style.display = "block";
                }
                else {
                    listItems[i].style.display = "none";
                }
            }
        }
    };

    /*
     Parse .rdf external file? (If not feasable, then look into PHP doing this)
     
    */
    
    Radify.load_ontology = function(ontology) {
        
    };
    
    function echo(s) {
        document.getElementById("output").innerHTML = s;
    }
    
    window.onload = function() {
        output = document.getElementById("output");
        launchButton = document.getElementById("launchRadify");
        Radify.event(launchButton, 'click', setOutput, false);
    };
    
    function setOutput() {
        loadCSS("style.css");
        Radify.loadView();
    }
    
    // cross-browser baseURI (trailing slash but not #)
    function getDocumentURI() {
        var loc = '';
        if((typeof document.baseURI) === 'string') {
            loc = document.baseURI;
        }
        else {
            loc = window.location.href;
        }
        // trailing slash but not #
        var endChar = loc.charAt(loc.length-1);
        if (endChar != "#" && endChar != "/") {
            loc += "/";
        }
        else if (endChar == "#") {
            loc = loc.slice(0,loc.length-1);
        }
        return loc;
    }
    
    // cross-browser setting XMLHTTP object (for AJAX/CORS)
    // needs changing for CORS
    function makeHttpObject() {
        try {return new XMLHttpRequest();}
        catch (error) {}
        // IE version 8+ supports this (for cross-site requests):
        try {return new XDomainRequest();}
        catch (error) {}
        // old, lame IE (can't use CORS)
        try {return new ActiveXObject("Msxml2.XMLHTTP");}
        catch (error) {}
        // old, lame IE (can't use CORS)
        try {return new ActiveXObject("Microsoft.XMLHTTP");}
        catch (error) {}
      
        throw new Error("Could not create HTTP request object.");
    }
    
})();